简单计算器 这里简单的实现一个计算器的 demo,最终的目的是希望在计算前后输出日志。
1 2 3 4 5 6 7 8 9 package com.itguigu.proxy;public interface MathI { int add (int i, int j) ; int sub (int i, int j) ; int mul (int i, int j) ; int div (int i, int j) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.itguigu.proxy;public class MathImpl implements MathI { @Override public int add (int i, int j) { System.out.println("add 接收到参数:" + i + "," + j); int result = i + j; System.out.println("add 结果返回:" + result); return result; } @Override public int sub (int i, int j) { System.out.println("sub 接收到参数:" + i + "," + j); int result = i - j; System.out.println("sub 结果返回:" + result); return result; } @Override public int mul (int i, int j) { System.out.println("mul 接收到参数:" + i + "," + j); int result = i * j; System.out.println("mul 结果返回:" + result); return result; } @Override public int div (int i, int j) { System.out.println("div 接收到参数:" + i + "," + j); int result = i / j; System.out.println("div 结果返回:" + result); return result; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.itguigu.proxy;public class Test { public static void main (String[] args) { MathI mathI = new MathImpl(); int add = mathI.add(1 , 1 ); System.out.println(add); } }
上面的每个操作的输出都显得非常的麻烦,可以考虑使用代理来优化。
动态代理 新建动态代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.itguigu.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;public class ProxyUtil { private MathImpl mathImpl; public ProxyUtil (MathImpl mathImpl) { super (); this .mathImpl = mathImpl; } public Object getProxy () { ClassLoader classLoader = this .getClass().getClassLoader(); Class<?>[] interfaces = mathImpl.getClass().getInterfaces(); return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { MyLogger.before(method.getName(), Arrays.toString(args)); Object result = method.invoke(mathImpl, args); MyLogger.after(method.getName(), result); return result; } }); } }
新建 MyLogger 类
1 2 3 4 5 6 7 8 9 10 11 package com.itguigu.proxy;public class MyLogger { public static void before (String methodName, String args) { System.out.println("方法 - " + methodName + " args:" + args); } public static void after (String methodName, Object result) { System.out.println("方法 - " + methodName + " result:" + result); } }
使用测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.itguigu.proxy;public class Test { public static void main (String[] args) { ProxyUtil paProxyUtil = new ProxyUtil(new MathImpl()); MathI mathI = (MathI) paProxyUtil.getProxy(); mathI.add(12 , 12 ); } }
代码地址
AOP AOP(Aspect-Oriented Programming,面向切面编程 ) 是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。AOP 编程操作的主要对象是切面 (aspect),而切面用于模块化横切关注点(公共功能) 。
AspectJ 在 Spring 中 AOP 的实现 导入JAR包
1 2 3 4 5 com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar
同样的实现之前的 MathI 接口和 MathImpl 实现类。并将 MathImpl 加上 @Component
注解
1 2 3 4 5 6 7 8 package com.atguigu.spring.aop;public interface MathI { int add (int i, int j) ; int sub (int i, int j) ; int mul (int i, int j) ; int div (int i, int j) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.atguigu.spring.aop;import org.springframework.stereotype.Component;@Component public class MathImpl implements MathI { @Override public int add (int i, int j) { int result = i + j; return result; } @Override public int sub (int i, int j) { int result = i - j; return result; } @Override public int mul (int i, int j) { int result = i * j; return result; } @Override public int div (int i, int j) { int result = i / j; return result; } }
定义一个类,加上 @Aspect
使其成为一个切面,并在其中使用 @Before
定义一个前置通知。并加上切入点表达式。并将改类加上 @Component
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atguigu.spring.aop;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;@Component @Aspect public class MyLoggerAspect { @Before (value = "execution(public int com.atguigu.spring.aop.MathImpl.add(int, int))" ) public void befordMethod () { System.out.println("前置通知" ); } }
新增 xml 配置文件,添加 spring 扫描,开启 aspectj 的自动代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" > <context:component-scan base-package ="com.atguigu.spring.aop" > </context:component-scan > <aop:aspectj-autoproxy > </aop:aspectj-autoproxy > </beans >
最后测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.spring.aop;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test { public static void main (String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop.xml" ); MathI mathI = applicationContext.getBean("mathImpl" , MathI.class ) ; int add = mathI.add(1 , 1 ); System.out.println(add); } }
切入点表达式 上面的例子中,只为了 add 方法添加前置通知,如果想为 MathImpl 接口下的所有方法 都添加前置通知,那么切入点表达式应该如下
1 @Before (value = "execution(public int com.atguigu.spring.aop.MathImpl.*(int, int))" )
MathImpl 接口下的所有方法,且任何访问修饰符和返回值 都可以
1 @Before (value = "execution(* com.atguigu.spring.aop.MathImpl.*(int, int))" )
MathImpl 接口下的所有方法,且可以是任何访问修饰符和返回值,可以是 aop 包下的任意的类,以及任意的参数 。(即第一个 表示任意的访问修饰符和返回值,第二个 代表 aop 下任意的类,第三个 * 代表任意的方法,.. 代表任意的参数。)
1 @Before (value = "execution(* com.atguigu.spring.aop.*.*(..))" )
为了验证上面切入点表达式,我们在 aop 下新建一个 TestHandler 类,并在里面创建一个无参的 test 方法。【注意⚠️:默认 JDK 的 AOP 实现是需要接口的,但是这里没有,原因是我们使用了 cglib,有了 cglib,那么依靠类的继承也能实现 AOP,TestHandler 默认是继承 Object 的。】
1 2 3 4 5 6 7 8 9 10 package com.atguigu.spring.aop;import org.springframework.stereotype.Component;@Component public class TestHandler { public void test () { System.out.println("测试切入点表达式" ); } }
测试效果
1 2 3 4 5 6 7 8 TestHandler bean = applicationContext.getBean("testHandler" , TestHandler.class ) ; bean.test();
前置通知 我们在 @Before
的 value 中传入了切入点表达式,所以我们也能在前置通知的方法参数重获取到切入点对象,从而获取到被代理方法的一些信息。@Before
作用于方法执行之前。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.atguigu.spring.aop;import java.util.Arrays;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;@Component @Aspect public class MyLoggerAspect { @Before (value = "execution(public int com.atguigu.spring.aop.MathImpl.add(int, int))" ) public void befordMethod (JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); String name = joinPoint.getSignature().getName(); System.out.println("method: " + name + " ,args: " + Arrays.toString(args)); System.out.println("前置通知" ); } }
后置通知 @After
将方法标注为后置通知。作用于方法的 finally 语句块,相当于不管有没有异常都会执行。
1 2 3 4 5 6 7 8 @After (value = "execution (* com.atguigu.spring.aop.*.*(..))" ) public void afterMethos () { System.out.println("后置通知" ); } }
返回通知 (最终通知) @AfterReturning
将方法标注为返回通知。相当于方法正确执行(无异常)之后。可以通过 returning 设置接收方法返回值的变量名(也就是说设置一个变量名称,用来接收方法的返回值,且这个返回值是 Object 类型的)。如果返回值要在方法中使用,那么必须在方法的形参中设置一个参数,名字和 returning 设置的变量名一致。
1 2 3 4 5 6 7 8 9 10 @AfterReturning (value = "execution (* com.atguigu.spring.aop.*.*(..))" , returning = "result" )public void afterReturningMethos (JoinPoint joinPoint, Object result) { System.out.println("method: " + joinPoint.getSignature().getName() + " ,result: " + result); System.out.println("返回通知" ); }
异常通知 @AfterThrowing
将方法标注为异常通知(例外通知)作用于方法抛出异常时, 可以通过 throwing 设置接收方法异常的信息, 在参数列表中可以通过具体的异常类型来对指定的异常信息进行操作。例如这里是 NullPointerException 异常。即当发生 NullPointerException 异常的时候,这个异常通知才起作用。
1 2 3 4 5 6 7 8 9 10 @AfterThrowing (value = "execution (* com.atguigu.spring.aop.*.*(..))" , throwing = "ex" )public void afterThrowingMethod (NullPointerException ex) { System.out.println("出现异常: " + ex); }
环绕通知 @Around
将方法标注为环绕通知,其实就是和动态代理一样,可以在方法执行的前后,加上自己想要处理的逻辑。不过参数类型为 ProceedingJoinPoint。proceed 代表执行方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Around (value = "execution (* com.atguigu.spring.aop.*.*(..))" )public Object aroundMethos (ProceedingJoinPoint proceedingJoinPoint) { Object result; try { System.out.println("前置通知" ); result = proceedingJoinPoint.proceed(); System.out.println("返回通知" ); return result; } catch (Throwable e) { e.printStackTrace(); System.out.println("异常通知" ); return -1 ; } finally { System.out.println("后置通知" ); } }
定义可以重复使用的切入点 当一个切入点表达式可以重复使用的时候,我们可以将其抽取成一个 Pointcut。
1 2 3 4 5 @Pointcut (value = "execution(* com.atguigu.spring.aop.*.*(..))" ) public void publicJoinPoint () {}
使用
1 2 @Before (value = "publicJoinPoint()" )public void befordMethod (JoinPoint joinPoint) {}
1 2 @Before (value = "publicJoinPoint()" )public void afterMethos () {}
1 2 @AfterReturning (value = "publicJoinPoint()" , returning = "result" )public void afterReturningMethos (JoinPoint joinPoint, Object result) {}
1 2 @AfterThrowing (value = "publicJoinPoint()" , throwing = "ex" )public void afterThrowingMethod (NullPointerException ex) {}
切面的优先级 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。可以用 @Order
注解指定切面的优先级大小,值越小,优先级越高,默认值为 int 的最大值。
新定义一个切面类, 并在其中定一个前置通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atguigu.spring.aop;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;@Component @Aspect public class TestAspectOrder { @Before (value = "execution(* com.atguigu.spring.aop.*.*(..))" ) public void befordMethod () { System.out.println("TestAspectOrder - 前置通知" ); } }
因为我们在 MyLoggerAspect 中也有一个前置通知,也是作用于切入点 execution(* com.atguigu.spring.aop.*.*(..))
上的。所以这里就存在一个优先级的问题,可以加上 @Order
来指定切面的优先级。
1 2 3 4 5 6 @Component @Aspect @Order (1 ) public class MyLoggerAspect { .... }
这时,因为 TestAspectOrder 没有设置优先级,而 MyLoggerAspect 的优先级是 1,所以就会执行 MyLoggerAspect 中的前置通知。
Spring 原生 AOP 的实现 除了使用 AspectJ 注解声明切面,Spring 也支持在 bean 配置文件中声明切面。这种声明是通过 aop 名称空间中的 XML 元素完成的。而基于 XML 的配置则是 Spring 专有的。
同样定义 MathI 接口和 MathImpl 接口实现类,并新建 MyLogger 类作为切面类。
1 2 3 4 5 6 7 8 9 10 package com.atguigu.spring.aopxml;import org.springframework.stereotype.Component;@Component public class MyLogger { public void before () { System.out.println("前置通知" ); } }
新建 aop_xml 配置文件进行 aop 配置。xml 中首先要进行组件的扫描,其次使用 ref 关联切面类,最后定义各种类型的通知。同时也支持将切入点表达式进行抽取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" > <context:component-scan base-package ="com.atguigu.spring.aopxml" > </context:component-scan > <aop:config > <aop:aspect ref ="myLogger" > <aop:pointcut expression ="execution(* com.atguigu.spring.aopxml.*.*(..))" id ="pointcut" /> <aop:before method ="before" pointcut-ref ="pointcut" > </aop:before > </aop:aspect > </aop:config > </beans >
新建测试类进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.atguigu.spring.aopxml;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test { public static void main (String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop_xml.xml" ); MathI mathI = applicationContext.getBean("mathImpl" , MathI.class ) ; int result = mathI.add(1 , 1 ); System.out.println(result); } }
代码地址